﻿using Core.Entities;
using Core.Formats;
using Core.ReadWriter;
using Core.Tools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Core.Service
{

    public class WxPay: BaseService
    {
        private static readonly XmlFormat Format = new XmlFormat();

        private static readonly string WxPayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        private readonly string AppId;
        private readonly string MchId;
        private readonly string Key;


        private readonly List<KeyValuePair<string, object>> Params;
        /// <summary>
        /// 随机码
        /// </summary>
        private readonly string NonceStr;

        /// <summary>
        /// 用户自定义商品 ID，当 TradeType 为 NATIVE 时，此值是必须的
        /// </summary>
        public string ProductId { get; set; }
        /// <summary>
        /// 用户标识，当 TradeType 为 JSAPI 时，此值是必须的
        /// </summary>
        public string OpenId { get; set; }
        /// <summary>
        /// 设备号
        /// </summary>
        public string DeviceInfo { get; set; }
        /// <summary>
        /// 签名类型
        /// </summary>
        public WxSignTypes? SignType { get; set; }
        /// <summary>
        /// 商品详情
        /// </summary>
        public string Detail { get; set; }
        /// <summary>
        /// 附加数据
        /// </summary>
        public string Attach { get; set; }
        /// <summary>
        /// 标价币种
        /// </summary>
        public WxFeeTypes? FeeType { get; set; }
        /// <summary>
        /// 交易起始时间
        /// </summary>
        public DateTime? TimeStart { get; set; }
        /// <summary>
        /// 交易结束时间
        /// </summary>
        public DateTime? TimeExpire { get; set; }
        /// <summary>
        /// 商品标记
        /// </summary>
        public string GoodsTag { get; set; }
        /// <summary>
        /// 指定支付方式，上传此参数 "no_credit" 可限制用户不能使用信用卡支付
        /// </summary>
        public string LimitPay { get; set; }

        public WxPay(string AppId, string MchId, string Key)
        {
            this.AppId = AppId;
            this.MchId = MchId;
            this.Key = Key;

            NonceStr = Guid.NewGuid().ToString("N");

            Params = new List<KeyValuePair<string, object>>();
        }
        /// <summary>
        /// 计算签名
        /// </summary>
        /// <param name="Params"></param>
        /// <returns></returns>
        private string Sign
        {
            get
            {
                WxSignTypes SignType;

                if (this.SignType == null)
                {
                    SignType = WxSignTypes.MD5;
                }
                else
                {
                    SignType = (WxSignTypes)this.SignType;
                }

                if (SignType != WxSignTypes.MD5)
                {
                    throw new Exception("暂只支持 MD5 签名");
                }

                Params.Sort((Before, After) =>
                {
                    return Before.Key.CompareTo(After.Key);
                });

                var TextBuilder = new StringBuilder();

                foreach (var Item in Params)
                {
                    TextBuilder.Append(Item.Key);
                    TextBuilder.Append("=");
                    TextBuilder.Append(AsString(Item.Value));
                    TextBuilder.Append("&");
                }

                TextBuilder.Append("key=");
                TextBuilder.Append(Key);

                return StringHelper.MD5(TextBuilder.ToString()).ToString("N").ToUpper();
            }
        }

        /// <summary>
        /// 添加参数
        /// </summary>
        private void AddParam(string Name, object Value)
        {
            if (Value != null)
            {
                Params.Add(new KeyValuePair<string, object>(Name, Value));
            }
        }

        /// <summary>
        /// 生成 XML
        /// </summary>
        /// <returns></returns>
        private string GenerateXML()
        {
            StringBuilder XMLBuilder = new StringBuilder();

            XMLBuilder.Append("<xml>");

            foreach (var Item in Params)
            {
                XMLBuilder.Append("<");
                XMLBuilder.Append(Item.Key);
                XMLBuilder.Append(">");

                var Context = AsString(Item.Value);

                if (Context.IndexOf("<") != -1)
                {
                    XMLBuilder.Append("<![CDATA[{");
                    XMLBuilder.Append(Context);
                    XMLBuilder.Append("}]]>");
                }
                else
                {
                    XMLBuilder.Append(Context);
                }


                XMLBuilder.Append("</");
                XMLBuilder.Append(Item.Key);
                XMLBuilder.Append(">");
            }

            XMLBuilder.Append("</xml>");

            return XMLBuilder.ToString();
        }

        /// <summary>
        /// 参数转 String 值
        /// </summary>
        private string AsString(object Object)
        {
            if (Object == null)
            {
                return "";
            }
            if (Object is DateTime)
            {
                return ((DateTime)Object).ToString("yyyyMMddHHmmss");
            }

            return Object.ToString();
        }

        /// <summary>
        /// 生成订单
        /// </summary>
        /// <param name="Body">商品描述</param>
        /// <param name="OutTradeNo">商户订单号</param>
        /// <param name="TotalFee">标价金额</param>
        /// <param name="SpbillCreateIp">终端IP</param>
        /// <param name="NotifyUrl">通知地址</param>
        /// <param name="TradeType">交易类型</param>
        public WxPayResult UnifiedOrder(string Body, string OutTradeNo, int TotalFee, string SpbillCreateIp, string NotifyUrl, WxTradeTypes TradeType)
        {
            if (TradeType == WxTradeTypes.NATIVE && ProductId == null)
            {
                throw new Exception("当 TradeType 为 NATIVE 时， ProductId 是必须的。");
            }

            if (TradeType == WxTradeTypes.JSAPI && OpenId == null)
            {
                throw new Exception("当 TradeType 为 JSAPI 时， OpenId 是必须的。");
            }

            AddParam("appid", AppId);
            AddParam("mch_id", MchId);
            AddParam("device_info", DeviceInfo);
            AddParam("nonce_str", NonceStr);
            AddParam("sign_type", SignType);
            AddParam("body", Body);
            AddParam("detail", Detail);
            AddParam("attach", Attach);
            AddParam("out_trade_no", OutTradeNo);
            AddParam("fee_type", FeeType);
            AddParam("total_fee", TotalFee);
            AddParam("spbill_create_ip", SpbillCreateIp);
            AddParam("time_start", TimeStart);
            AddParam("time_expire", TimeExpire);
            AddParam("goods_tag", GoodsTag);
            AddParam("notify_url", NotifyUrl);
            AddParam("trade_type", TradeType);
            AddParam("product_id", ProductId);
            AddParam("limit_pay", LimitPay);
            AddParam("openid", OpenId);

            /* 最后添加签名 */
            AddParam("sign", Sign);

            var Box = new XmlBox<WxPayResult>();

            var ResultText = PostRequest(WxPayUrl, GenerateXML(), "text/xml; charset=utf-8");

            Format.Decode(ResultText, ObjectReadWriter.Create(Box));

            return Box.xml;
        }
    }
    public enum WxSignTypes
    {
        MD5
    }
    public enum WxTradeTypes
    {
        /// <summary>
        /// 公众号支付
        /// </summary>
        JSAPI,
        /// <summary>
        /// 扫码支付
        /// </summary>
        NATIVE,
        /// <summary>
        /// App 支付
        /// </summary>
        APP,
    }
    public enum WxFeeTypes
    {
        CNY = 0,
    }
}
